﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace OCRTheNewHope
{
    class DecisionTree
    {
        public Forest Owner { get; private set; }
        public Digit[] Inputs { get; private set; }
        public DecisionNode Root { get; private set; }
        public int TreeIndex { get; private set; }

        public int TotalTrainDigits { get; private set; }
        public int TotalTestDigits { get; private set; }

        public int TrainError { get; private set; }
        public int TestError { get; private set; }

        public double TrainAccuracy { get; private set; }
        public double TestAccuracy { get; private set; }

        public string LogString { get; private set; }

        private List<DecisionNode> NodeJobList = new List<DecisionNode>();

        public DecisionTree(Forest owner, Digit[] trainDigits, Digit[] testDigits, int treeIndex)
        {
            this.Owner = owner;
            this.Inputs = trainDigits;
            this.TreeIndex = treeIndex;

            this.Root = new DecisionNode(this, null, this.Inputs);

            this.NodeJobList.Add(this.Root);
            FillTree();

            this.TotalTrainDigits = trainDigits.Length;
            this.TotalTestDigits = testDigits.Length;

            this.TrainError = this.Test(trainDigits);
            this.TestError = this.Test(testDigits);

            this.TrainAccuracy = ((double)(this.TotalTrainDigits - this.TrainError) / this.TotalTrainDigits) * 100;
            this.TestAccuracy = ((double)(this.TotalTestDigits - this.TestError) / this.TotalTestDigits) * 100;

            this.Log();

            this.Inputs = null;            
        }

        private void FillTree()
        {
            while (this.NodeJobList.Count > 0)
            {
                DecisionNode currentNode = this.NodeJobList[0];

                DecisionNode[] branches = currentNode.Split();
                if (branches != null)
                {
                    this.NodeJobList.AddRange(branches);
                }

                this.NodeJobList.RemoveAt(0);
            }
        }

        public byte Compute(Digit d)
        {
            DecisionNode currentNode = this.Root;

            while (!currentNode.IsLeaf)
            {
                currentNode = currentNode.PassToChild(d);
            }

            return (byte)currentNode.Output.Value;
        }

        public double[] ComputeWithHistogram(Digit d)
        {
            DecisionNode currentNode = this.Root;

            while (!currentNode.IsLeaf)
            {
                currentNode = currentNode.PassToChild(d);
            }

            return currentNode.HistogramOutput;
        }

        public int Test(Digit[] testDigits)
        {
            int numTrue = 0;
            int numFalse = 0;

            for (int i = 0; i < testDigits.Length; i++)
            {
                byte value = this.Compute(testDigits[i]);
                if (value == testDigits[i].DigitValue)
                    numTrue++;
                else
                    numFalse++;
            }

            return numFalse;
        }

        public void Log()
        {
            LogString = "";
            LogString += this + " statistics:" + Environment.NewLine;
            LogString += "----------------" + Environment.NewLine;
            LogString += "Train error: " + this.TrainError + "/" + this.TotalTrainDigits + " ( " + this.TrainAccuracy + "% Accuracy )" + Environment.NewLine;
            LogString += "Test error: " + this.TestError + "/" + this.TotalTestDigits + " ( " + this.TestAccuracy + "% Accuracy )" + Environment.NewLine;
        }

        public override string ToString()
        {
            return "Tree " + this.TreeIndex.ToString();
        }
    }
}
